﻿using System;
using System.Collections.Generic;
using System.Linq;
using Ewk.MachineLearning.Genetic.Programming.Program.Functions;
using Ewk.MachineLearning.Genetic.Programming.Program.Terminals;

namespace Ewk.MachineLearning.Genetic.Programming.Engine
{
    /// <summary>
    /// Contains properties of the that define the problem to be solved.
    /// </summary>
    public class ProblemDefinition
    {
        /// <summary>
        /// Initializes a new instance.
        /// </summary>
        /// <param name="testSet">The data to learn from.</param>
        /// <param name="variables">The variables that define the input.</param>
        /// <param name="fitnessCalculator">Calculates the fitness of individuals.</param>
        public ProblemDefinition(IEnumerable<Exemplar> testSet, IEnumerable<Variable> variables, IFitnessCalculator fitnessCalculator)
        {
            FitnessCalculator = fitnessCalculator;
            TestSet = testSet.ToList();

            Variables = variables;

            var examplar = TestSet.First();
            OutputType = examplar.ExpectedOuput.GetType();

            ValidateExamplars();
        }

        /// <summary>
        /// The constants that can be used by individuals.
        /// </summary>
        public IEnumerable<Constant> Constants { get; set; }

        /// <summary>
        /// The functions that can be used by individuals.
        /// </summary>
        public IEnumerable<Function> Functions { get; set; }

        /// <summary>
        /// The data to be processed and to compare results with.
        /// </summary>
        public IEnumerable<Exemplar> TestSet { get; private set; }

        /// <summary>
        /// The variables that can be used by individuals.
        /// </summary>
        public IEnumerable<Variable> Variables { get; private set; }

        /// <summary>
        /// The <see cref="Type"/> of the result.
        /// </summary>
        public Type OutputType { get; private set; }

        /// <summary>
        /// The calculator of the fitness of an individual.
        /// </summary>
        public IFitnessCalculator FitnessCalculator { get; private set; }

        private void ValidateExamplars()
        {
            foreach (var exemplar in TestSet)
            {
                ValidateExamplar(exemplar);
            }
        }

        private void ValidateExamplar(Exemplar exemplar)
        {
            if (exemplar.Input.Count() != Variables.Count())
            {
                throw new InvalidOperationException("Inconsistent number of parameters");
            }

            var typeCompare = Variables
                .Zip(exemplar.Input, (variable, input) => variable.OutputDefinition.Types.Any(type => type == input.GetType()))
                .ToList();

            if (typeCompare.Any(o => !o))
            {
                throw new InvalidOperationException("Inconsistent parameter types");
            }

            if (OutputType != exemplar.ExpectedOuput.GetType())
            {
                throw new InvalidOperationException("Inconsistent output types");
            }
        }
    }
}